/****************************************************************************/
/*  File:       PkgHandlerSaxon.java                                        */
/*  Author:     F. Georges                                                  */
/*  Company:    H2O Consulting                                              */
/*  Date:       2009-11-12                                                  */
/*  Tags:                                                                   */
/*      Copyright (c) 2009 Florent Georges (see end of file.)               */
/* ------------------------------------------------------------------------ */


package org.expath.pkg.repo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Transformer;

/**
 * Handle a Saxon element in the package descriptor.
 *
 * The structure of the Saxon element is as following (each child is optional
 * and repeatable, and can be in any order):
 *
 * <pre>
 * &lt;saxon>
 *    &lt;jar>...&lt;/jar>
 *    &lt;class>...&lt;/class>
 *    &lt;xslt-wrapper>
 *       &lt;import-uri>...&lt;/import-uri>
 *       &lt;file>...&lt;/file>
 *    &lt;xslt-wrapper>
 *    &lt;xquery-wrapper>
 *       &lt;namespace>...&lt;/namespace>
 *       &lt;file>...&lt;/file>
 *    &lt;xquery-wrapper>
 *    &lt;xslt>
 *       &lt;import-uri>...&lt;/import-uri>
 *       &lt;file>...&lt;/file>
 *    &lt;xslt>
 *    &lt;xquery>
 *       &lt;namespace>...&lt;/namespace>
 *       &lt;file>...&lt;/file>
 *    &lt;xquery>
 * &lt;saxon>
 * </pre>
 *
 * @author Florent Georges
 * @date   2009-11-12
 */
class PkgHandlerSaxon
        implements PkgComponentHandler
{
    PkgHandlerSaxon(Repository repo, PkgInstaller installer)
    {
        myRepo = repo;
        myInstaller = installer;
    }

    public void handleDescription(XMLStreamReader parser)
            throws PackageException
    {
        try {
            parser.next();
            while ( parser.getEventType() == XMLStreamConstants.START_ELEMENT ) {
                if ( PkgInstaller.PKG_NS.equals(parser.getNamespaceURI()) ) {
                    handleComponent(parser, parser.getLocalName());
                }
                else {
                    // ignore elements not in the EXPath Pkg namespace
                    // TODO: FIXME: Actually ignore (pass it.)
                }
                parser.next();
            }
            // position to </saxon>
            parser.next();
        }
        catch ( XMLStreamException ex ) {
            throw new PackageException("Error reading the package descriptor", ex);
        }
    }

    private void handleComponent(XMLStreamReader parser, String local)
            throws PackageException, XMLStreamException
    {
        if ( "jar".equals(local) ) {
            myJars.add(myInstaller.getElementValue(parser));
        }
        else if ( "function".equals(local) ) {
            myFuns.add(myInstaller.getElementValue(parser));
        }
        else if ( "xslt".equals(local) ) {
            Mapping m = handleMapping(parser, "import-uri");
            myXslt.put(m.getUri(), m.getFile());
        }
        else if ( "xquery".equals(local) ) {
            Mapping m = handleMapping(parser, "namespace");
            myXquery.put(m.getUri(), m.getFile());
        }
        else if ( "xslt-wrapper".equals(local) ) {
            Mapping m = handleMapping(parser, "import-uri");
            myXsltWrappers.put(m.getUri(), m.getFile());
        }
        else if ( "xquery-wrapper".equals(local) ) {
            Mapping m = handleMapping(parser, "namespace");
            myXqueryWrappers.put(m.getUri(), m.getFile());
        }
        else {
            throw new PackageException("Unknown Saxon component type: " + local);
        }
    }

    private Mapping handleMapping(XMLStreamReader parser, String uri_name)
            throws PackageException, XMLStreamException
    {
        myInstaller.ensureNextElement(parser, uri_name, true);
        String uri = myInstaller.getElementValue(parser);
        myInstaller.ensureNextElement(parser, "file", true);
        String file = myInstaller.getElementValue(parser);
        // position to </...> (xslt, xquery, xslt-wrapper or xquery-wrapper)
        parser.next();
        return new Mapping(uri, file);
    }

    /**
     * ...
     */
    public void installComponents(File target)
            throws PackageException
    {
        File dir = new File(myRepo.getRoot(), ".expath-pkg/");

        // myJars and myClasses
        //   -> .expath-pkg/[lib]/saxon/classpath.txt
        //   -> .expath-pkg/[lib]/saxon/classes.xml
        File libdir = new File(dir, target.getName());
        // ensure it (TODO: should be done before, in PkgInstaller, once for all,
        // as part of what it ensure for the handlers...)
        libdir.mkdir();
        File saxon = new File(libdir, "saxon/");
        saxon.mkdir();
        String relative = "../../../" + target.getName() + "/";
        writeClasspath(new File(saxon, "classpath.txt"), target);
        writeClasses(new File(saxon, "classes.xml"), relative);

        // myXslt and myXquery
        //   -> .expath-pkg/[lib]/saxon/[xslt|xquery]-catalog.xml
        writeCatalog(myXquery, dir, target, "xquery", "catalog", false);
        writeCatalog(myXslt, dir, target, "xslt", "catalog", false);

        // myXsltWrappers and myXqueryWrappers
        //   -> .expath-pkg/[lib]/saxon/[xslt|xquery]-wrappers.xml
        //   -> .expath-pkg/[lib]/saxon/[xslt|xquery]-empty.xml
        writeCatalog(myXqueryWrappers, dir, target, "xquery", "wrappers", false);
        writeCatalog(myXsltWrappers, dir, target, "xslt", "wrappers", false);

        // in case of XQuery, each empty wrapper must point ot a generated
        // XQuery library module, with the correct namespace, but empty
        writeEmptyXqueryWrappers(myXqueryWrappers, saxon);
        writeCatalog(myXqueryWrappers, dir, target, "xquery", "empty", true);

        // in case of XSLT, all empty wrappers point to the same empty stylesheet
        for ( Map.Entry<String, String> e : myXsltWrappers.entrySet() ) {
            e.setValue("../.expath-pkg/.saxon/empty-wrapper.xsl");
        }
        writeCatalog(myXsltWrappers, dir, target, "xslt", "empty", false);
    }

    private void writeEmptyXqueryWrappers(Map<String, String> wrappers, File dir)
            throws PackageException
    {
        int i = 1;
        for ( Map.Entry<String, String> e : wrappers.entrySet() ) {
            String name = "empty-wrapper-" + Integer.toString(i, 16) + ".xq";
            File f = new File(dir, name);
            try {
                FileWriter out = new FileWriter(f);
                out.write("module namespace tns = \"");
                out.write(e.getKey());
                out.write("\";\n");
                out.close();
            }
            catch ( IOException ex ) {
                throw new PackageException("Error writing the Saxon empty XQuery wrapper: " + f, ex);
            }
            e.setValue(name);
            ++i;
        }
    }

    private void writeClasspath(File target, File libdir)
            throws PackageException
    {
        if ( target.exists() ) {
            throw new PackageException("The Saxon classpath file exists: " + target);
        }
        if ( myJars.isEmpty() ) {
            // do not create the file if empty
            return;
        }
        try {
            FileWriter out = new FileWriter(target);
            for ( String jar : myJars ) {
                File f = new File(libdir, jar);
                out.write(f.getAbsolutePath());
                out.write("\n");
            }
            out.close();
        }
        catch ( IOException ex ) {
            throw new PackageException("Error writing the Saxon classpath file: " + target, ex);
        }
    }

    private void writeClasses(File target, String relative)
            throws PackageException
    {
        if ( target.exists() ) {
            throw new PackageException("The Saxon classes file exists: " + target);
        }
        if ( myJars.isEmpty() && myFuns.isEmpty() ) {
            // do not create the file if empty
            return;
        }
        try {
            OutputStream out = new FileOutputStream(target);
            writeUtf8(out, "<?xml version=\"1.0\"?>\n\n");
            writeUtf8(out, "<classes>\n");
            for ( String jar : myJars ) {
                writeUtf8(out, "   <jar>");
                writeUtf8(out, relative);
                writeUtf8(out, jar);
                writeUtf8(out, "</jar>\n");
            }
            for ( String jar : myFuns ) {
                writeUtf8(out, "   <function>");
                writeUtf8(out, jar);
                writeUtf8(out, "</function>\n");
            }
            writeUtf8(out, "</classes>\n");
            out.close();
        }
        catch ( IOException ex ) {
            throw new PackageException("Error writing the Saxon classes file: " + target, ex);
        }
    }

    /**
     * TODO: Try to merge with PkgInstaller.writeCatalog().
     *
     * In PkgInstaller:
     *     dir -> [repo]/.expath-pkg/
     *     target -> [repo]/[lib]
     *     kind -> xslt|xquery|...
     *
     * Here:
     *     dir -> [repo]/.expath-pkg/
     *     target -> [repo]/[lib]
     *     kind -> xslt|xquery|...
     *     suffix -> catalog|wrappers|empty
     */
    private void writeCatalog(Map<String, String> comps, File dir, File target, String kind, String suffix, boolean plain_values)
            throws PackageException
    {
        if ( comps.isEmpty() ) {
            // do not create the file if empty
            return;
        }
        // create the new catalog <repo>/.expath-pkg/<name>/saxon/<kind>-<suffix>.xml
        File subdir = new File(dir, target.getName());
        File saxon = new File(subdir, "saxon/");
        File catalog1 = new File(saxon, kind + "-" + suffix + ".xml");
        try {
            // TODO: Check return value!
            subdir.mkdir();
            saxon.mkdir();
            OutputStream out = new FileOutputStream(catalog1);
            writeUtf8(out, "<?xml version=\"1.0\"?>\n\n");
            writeUtf8(out, "<catalog xmlns=\"urn:oasis:names:tc:entity:xmlns:xml:catalog\">\n\n");
            for ( Map.Entry<String, String> e : comps.entrySet() ) {
                // system element
                writeUtf8(out, "   <system systemId=\"");
                writeUtf8(out, e.getKey());
                writeUtf8(out, "\"\n");
                writeUtf8(out, "           uri=\"");
                if ( plain_values ) {
                    writeUtf8(out, e.getValue());
                }
                else {
                    writeUtf8(out, "../../../" + target.getName() + "/" + e.getValue());
                }
                writeUtf8(out, "\"/>\n\n");
                // uri element
                writeUtf8(out, "   <uri name=\"");
                writeUtf8(out, e.getKey());
                writeUtf8(out, "\"\n");
                writeUtf8(out, "        uri=\"");
                if ( plain_values ) {
                    writeUtf8(out, e.getValue());
                }
                else {
                    writeUtf8(out, "../../../" + target.getName() + "/" + e.getValue());
                }
                writeUtf8(out, "\"/>\n\n");
            }
            writeUtf8(out, "</catalog>\n");
            out.close();
        }
        catch ( IOException ex ) {
            throw new PackageException("Error writing the catalog: " + catalog1, ex);
        }
        // update the catalog <repo>/.expath-pkg/.saxon/<kind>-<suffix>.xml
        // (if does not exist, create an empty one first)
        File saxon2 = new File(dir, ".saxon");
        File catalog2 = new File(saxon2, kind + "-" + suffix + ".xml");
        if ( ! catalog2.exists() ) {
            saxon2.mkdir();
            try {
                OutputStream out = new FileOutputStream(catalog2);
                writeUtf8(out, "<catalog xmlns=\"urn:oasis:names:tc:entity:xmlns:xml:catalog\"/>\n");
                out.close();
            }
            catch ( IOException ex ) {
                throw new PackageException("Error writing an empty catalog: " + catalog2, ex);
            }
        }
        Transformer trans = myInstaller.newXsltTransformer(PkgInstaller.XSLT_INSTALL_CAT);
        trans.setParameter("catalog", "../" + target.getName() + "/saxon/" + kind + "-" + suffix + ".xml");
        myInstaller.transformXmlFile(trans, saxon2, catalog2.getName());
    }

    private void writeUtf8(OutputStream out, String str)
            throws IOException
    {
        out.write(str.getBytes("utf-8"));
    }

    private Repository myRepo;
    private PkgInstaller myInstaller;
    private List<String> myJars = new ArrayList<String>();
    private List<String> myFuns = new ArrayList<String>();
    private Map<String, String> myXslt = new HashMap<String, String>();
    private Map<String, String> myXquery = new HashMap<String, String>();
    private Map<String, String> myXsltWrappers = new HashMap<String, String>();
    private Map<String, String> myXqueryWrappers = new HashMap<String, String>();

    private static class Mapping
    {
        public Mapping(String uri, String file) {
            myUri = uri;
            myFile = file;
        }
        public String getUri() {
            return myUri;
        }
        public String getFile() {
            return myFile;
        }
        private String myUri;
        private String myFile;
    }
}


/* ------------------------------------------------------------------------ */
/*  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS COMMENT.               */
/*                                                                          */
/*  The contents of this file are subject to the Mozilla Public License     */
/*  Version 1.0 (the "License"); you may not use this file except in        */
/*  compliance with the License. You may obtain a copy of the License at    */
/*  http://www.mozilla.org/MPL/.                                            */
/*                                                                          */
/*  Software distributed under the License is distributed on an "AS IS"     */
/*  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See    */
/*  the License for the specific language governing rights and limitations  */
/*  under the License.                                                      */
/*                                                                          */
/*  The Original Code is: all this file.                                    */
/*                                                                          */
/*  The Initial Developer of the Original Code is Florent Georges.          */
/*                                                                          */
/*  Contributor(s): none.                                                   */
/* ------------------------------------------------------------------------ */
